package query.delete

import ast.expr.SqlIdentifierExpr
import ast.statement.delete.SqlDelete
import dsl.column
import database.DB
import dsl.Query
import dsl.QueryTableColumn
import dsl.TableSchema
import query.ReviseQuery
import util.toSqlString
import visitor.getQueryExpr
import java.sql.Connection
import java.sql.SQLException
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties

/**
 * delete语句dsl类
 * @property db DB 数据库类型
 * @property conn Connection? 数据库连接
 * @property isTransaction Boolean 是否是事务
 * @property sqlDelete SqlDelete delete语法树
 */
class Delete(
    var db: DB = DB.MYSQL,
    override var conn: Connection? = null,
    override var isTransaction: Boolean = false
) : ReviseQuery() {
    constructor(db: DB) : this(db, null, false)

    private var sqlDelete = SqlDelete()

    /**
     * delete from子句
     * 例如：Delete() from "t1"
     * @param table String 表名
     * @return Delete 删除dsl
     */
    infix fun from(table: String): Delete {
        sqlDelete.table = SqlIdentifierExpr(table)
        return this
    }

    /**
     * delete from子句
     * 例如：Delete() from Table
     * @param table T 实体类伴生对象名
     * @return Delete 删除dsl
     */
    infix fun <T : TableSchema> from(table: T): Delete {
        return from(table.tableName)
    }

    /**
     * 根据实体数据生成按主键删除数据的sql
     * 例如：Delete() delete entity
     * @param entity Any 实体数据
     * @return Delete 删除dsl
     */
    infix fun delete(entity: Any): Delete {
        val clazz = entity::class
        val fields = clazz.declaredMemberProperties
            .map { it.name to it.getter.call(entity) }
            .toMap()

        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val companionClass = companion::class
        val table = companion as TableSchema
        from(table.tableName)

        val pkCols = companionClass.declaredMemberProperties
            .asSequence()
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to (it.second as QueryTableColumn) }
            .filter { it.second.primaryKey }
            .map { it.second.column to fields[it.first] }
            .toMap()

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        pkCols.forEach {
            if (it.value == null) {
                throw SQLException("主键为空")
            } else {
                where(column(it.key) eq it.value)
            }
        }
        return this
    }

    /**
     * 生成按主键删除数据的sql
     * 例如：Delete().delete<Table>(1)
     * @param primaryKey Any 主键的值
     * @return Delete 删除dsl
     */
    @JvmName("deleteById")
    inline infix fun <reified T> delete(primaryKey: Any): Delete {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.getter.call(companion) }
            .filterIsInstance<QueryTableColumn>()
            .filter { it.primaryKey }

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size > 1) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        val pkCol = pkCols[0]

        return from(tableName).where(pkCol eq primaryKey)
    }

    /**
     * 生成按主键删除数据的sql（联合主键）
     * @param primaryKeys List<Pair<String, Any>> 主键的值
     * @return Delete 删除dsl
     */
    inline fun <reified T> delete(primaryKeys: List<Pair<String, Any>>): Delete {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }
            .filter { it.second.primaryKey }
            .toMap()

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size != primaryKeys.size) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        from(tableName)
        primaryKeys.forEach {
            if (pkCols.containsKey(it.first)) {
                where(pkCols[it.first]!! eq it.second)
            }
        }

        return this
    }

    /**
     * 生成按主键删除数据的sql（联合主键）
     * @param primaryKeys Map<String, Any> 主键的值
     * @return Delete 删除dsl
     */
    inline fun <reified T> delete(primaryKeys: Map<String, Any>): Delete {
        return delete<T>(primaryKeys.map { it.key to it.value })
    }

    /**
     * where子句（如果有多个where调用，会使用AND拼接条件）
     * @param condition Query where条件中的表达式
     * @return Delete 删除dsl
     */
    infix fun where(condition: Query): Delete {
        this.sqlDelete.addCondition(getQueryExpr(condition, this.db).expr)
        return this
    }

    /**
     * where子句（当前面的lambda返回值为true的时候将条件拼接进sql）
     * @param test Function0<Boolean> 当此参数返回值为true时将condition拼接进sql
     * @param condition Query 条件表达式
     * @return Delete 删除dsl
     */
    fun where(test: () -> Boolean, condition: Query): Delete {
        if (test()) {
            where(condition)
        }

        return this
    }

    /**
     * where子句（当前面的参数为true的时候将条件拼接进sql）
     * @param test Boolean 当此参数为true时将condition拼接进sql
     * @param condition Query 条件表达式
     * @return Delete 删除dsl
     */
    fun where(test: Boolean, condition: Query): Delete {
        if (test) {
            where(condition)
        }

        return this
    }

    /**
     * 生成sql
     * @return String sql语句
     */
    override fun sql() = toSqlString(sqlDelete, db)
}